import applyMixin from './mixin'
import devtoolPlugin from './plugins/devtool'
import ModuleCollection from './module/module-collection'
import { forEachValue, isObject, isPromise, assert, partial } from './util' 

let Vue // bind on install

export class Store {
  constructor (options = {}) {
    // Auto install if it is not done yet and `window` has `Vue`.
    // To allow users to avoid auto-installation in some cases,
    // this code should be placed here. See #731
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }

    const {
      plugins = [],
      strict = false
    } = options

    // store internal state
    this._committing = false//就是一个修改state的开关
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._watcherVM = new Vue()
    this._makeLocalGettersCache = Object.create(null)

    // bind commit and dispatch to self
    const store = this
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // strict mode
    this.strict = strict
    const state = this._modules.root.state

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    //installModule 方法是把我们通过 根store进行初始化 传入的各种属性模块注册和安装
    installModule(this, state, [], this._modules.root)

    // initialize the store vm, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    //
    resetStoreVM(this, state)

    // apply plugins
    plugins.forEach(plugin => plugin(this))

    const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
    if (useDevtools) {
      devtoolPlugin(this)
    }
  }
  //提供get方法禁用set方法
  get state () {
    //这里返回state
    return this._vm._data.$$state
  }

  set state (v) {
    // if (__DEV__) {
    //   assert(false, `use store.replaceState() to explicit replace store state.`)
    // }
  }
  /**
   * 就是用来修改state
   */
  commit (_type, _payload, _options) {
    debugger;
    // 检查对象风格的提交，主要是针对当type是个对象的情况，需要把这个对象解析出来
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    //获取对应的执行函数
    const entry = this._mutations[type]
    //如果找不到
    if (!entry) {
      // if (__DEV__) {
      //   console.error(`[vuex] unknown mutation type: ${type}`)
      // }
      return
    }
    this._withCommit(() => {
      //这里就是我们同过registerMutation函数向this._mutations添加的数组
      entry.forEach(function commitIterator (handler) {
        //调用函数,传入参数
        handler(payload)
      })
    })

    this._subscribers
      .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
      .forEach(sub => sub(mutation, this.state))

    // if (
    //   __DEV__ &&
    //   options && options.silent
    // ) {
    //   console.warn(
    //     `[vuex] mutation type: ${type}. Silent option has been removed. ` +
    //     'Use the filter functionality in the vue-devtools'
    //   )
    // }
  }

  dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
    if (!entry) {
      // if (__DEV__) {
      //   console.error(`[vuex] unknown action type: ${type}`)
      // }
      return
    }

    try {
      this._actionSubscribers
        .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
        .filter(sub => sub.before)
        .forEach(sub => sub.before(action, this.state))
    } catch (e) {
      // if (__DEV__) {
      //   console.warn(`[vuex] error in before action subscribers: `)
      //   console.error(e)
      // }
    }

    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)

    return new Promise((resolve, reject) => {
      result.then(res => {
        try {
          this._actionSubscribers
            .filter(sub => sub.after)
            .forEach(sub => sub.after(action, this.state))
        } catch (e) {
          // if (__DEV__) {
          //   console.warn(`[vuex] error in after action subscribers: `)
          //   console.error(e)
          // }
        }
        resolve(res)
      }, error => {
        try {
          this._actionSubscribers
            .filter(sub => sub.error)
            .forEach(sub => sub.error(action, this.state, error))
        } catch (e) {
          // if (__DEV__) {
          //   console.warn(`[vuex] error in error action subscribers: `)
          //   console.error(e)
          // }
        }
        reject(error)
      })
    })
  }

  subscribe (fn, options) {
    return genericSubscribe(fn, this._subscribers, options)
  }

  subscribeAction (fn, options) {
    const subs = typeof fn === 'function' ? { before: fn } : fn
    return genericSubscribe(subs, this._actionSubscribers, options)
  }

  watch (getter, cb, options) {
    // if (__DEV__) {
    //   assert(typeof getter === 'function', `store.watch only accepts a function.`)
    // }
    return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
  }

  replaceState (state) {
    this._withCommit(() => {
      this._vm._data.$$state = state
    })
  }

  registerModule (path, rawModule, options = {}) {
    if (typeof path === 'string') path = [path]

    // if (__DEV__) {
    //   assert(Array.isArray(path), `module path must be a string or an Array.`)
    //   assert(path.length > 0, 'cannot register the root module by using registerModule.')
    // }

    this._modules.register(path, rawModule)
    installModule(this, this.state, path, this._modules.get(path), options.preserveState)
    // reset store to update getters...
    resetStoreVM(this, this.state)
  }

  unregisterModule (path) {
    if (typeof path === 'string') path = [path]

    // if (__DEV__) {
    //   assert(Array.isArray(path), `module path must be a string or an Array.`)
    // }

    this._modules.unregister(path)
    this._withCommit(() => {
      const parentState = getNestedState(this.state, path.slice(0, -1))
      Vue.delete(parentState, path[path.length - 1])
    })
    resetStore(this)
  }

  hasModule (path) {
    if (typeof path === 'string') path = [path]


    return this._modules.isRegistered(path)
  }

  hotUpdate (newOptions) {
    this._modules.update(newOptions)
    resetStore(this, true)
  }
  /**
   * 由于我们是在修改 state，Vuex 中所有对 state 的修改都会用 _withCommit函数包装，
   * 保证在同步修改 state 的过程中 this._committing 的值始终为true。这样当我们观测 state 的变化时，
   * 如果 this._committing 的值不为 true，则能检查到这个状态修改是有问题的。
   */
  _withCommit (fn) {
    //起始为false
    const committing = this._committing
    //改变状态
    this._committing = true
    fn()
    //最后关闭
    this._committing = committing
  }
}

function genericSubscribe (fn, subs, options) {
  if (subs.indexOf(fn) < 0) {
    options && options.prepend
      ? subs.unshift(fn)
      : subs.push(fn)
  }
  return () => {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}

function resetStore (store, hot) {
  store._actions = Object.create(null)
  store._mutations = Object.create(null)
  store._wrappedGetters = Object.create(null)
  store._modulesNamespaceMap = Object.create(null)
  const state = store.state
  // init all modules
  installModule(store, state, [], store._modules.root, true)
  // reset vm
  resetStoreVM(store, state, hot)
}
/**
 * 这个函数就是将我们的state和getter实例化为vue中的data,computed
 */
function resetStoreVM (store, state, hot) {
  //这部分留了现有的 store._vm 对象
  const oldVm = store._vm;
  //后面基本就是让我们通过this.$store.getters.xxxgetters访问到store._vm[xxxgetters]
  // bind store public getters
  store.getters = {}
  // reset local getters cache
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  forEachValue(wrappedGetters, (fn, key) => {
    // use computed to leverage its lazy-caching mechanism
    // direct inline function use will lead to closure preserving oldVm.
    // using partial to return function with only arguments preserved in closure environment.
    computed[key] = partial(fn, store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // for local getters
    })
  })

  // use a Vue instance to store the state tree
  // suppress warnings just in case the user has added
  // some funky global mixins
  //这部分的代码首先先拿全局 Vue.config.silent 的配置，然后临时把这个配置设成 true，
  const silent = Vue.config.silent
  Vue.config.silent = true;
  //然后实例化vue,传入我们的getter和data,这里就可以看到其实vuex的state就是data,getter就是computed
  store._vm = new Vue({
    //这里没有直接将state给state而是给了$$属性,主要是怕用户直接data,安全考虑
    data: {
      $$state: state
    },
    computed
  })
  //然后重置
  Vue.config.silent = silent
  /**
   * 解释一个为啥要开启silent:
   * 设置 silent 为 true 的目的是为了取消这个 _vm 的所有日志和警告。
   * 把 computed 对象作为 _vm 的 computed 属性，这样就完成了 getters 的注册。
   * 因为当我们在组件中访问 this.$store.getters.xxxgetters 的时候，就相当于访问 store._vm[xxxgetters]，
   * 也就是在访问 computed[xxxgetters]，这样就访问到了 xxxgetters 对应的回调函数了
   */

  // enable strict mode for new vm
  if (store.strict) {
    enableStrictMode(store)
  }
  /**
   * 这里的逻辑很简单，由于这个函数每次都会创建新的 Vue 实例并赋值到 store._vm 上，
   * 那么旧的 _vm 对象的状态设置为 null，并调用 $destroy 方法销毁这个旧的 _vm 对象。
   */
  if (oldVm) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldVm._data.$$state = null
      })
    }
    Vue.nextTick(() => oldVm.$destroy())
  }
}

function installModule (store, rootState, path, module, hot) {
  //判断是否有module模块
  const isRoot = !path.length
  //获取 key/ 拼接的字符路径
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  //在命名空间中注册
  if (module.namespaced) {
    // if (store._modulesNamespaceMap[namespace] && __DEV__) {
    //   console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    // }
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  if (!isRoot && !hot) {
    //这里获取根store
    /**
     * 就是根据 path 查找 state 上的嵌套 state。在这里就是传入 rootState 和 path，计算出当前模块的父模块的 state，
     * 由于模块的 path 是根据模块的名称 concat 连接的，所以 path 的最后一个元素就是当前模块的模块名，最后调用
     */
    const parentState = getNestedState(rootState, path.slice(0, -1))
    //获取模块名字
    const moduleName = path[path.length - 1]
    //调用方法
    store._withCommit(() => {
      // if (__DEV__) {
      //   if (moduleName in parentState) {
      //     console.warn(
      //       `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
      //     )
      //   }
      // }
      //把当前模块的 state 添加到 parentState(rootStore) 中,
      //这就是我们在有组件中访问根state,可以访问到所有子模块的state原因
      //只是这里只是先挂载到module.root中
      Vue.set(parentState, moduleName, module.state)
    })
  }
  //这里传入store
  const local = module.context = makeLocalContext(store, namespace, path)

  //这里下面的见面思议,分别处理,Mutation,Action,Getter
  module.forEachMutation((mutation, key) => {
    //这里莫名空间加上,当前模块名
    const namespacedType = namespace + key;
    //添加将Mutation添加到一个数组中
    registerMutation(store, namespacedType, mutation, local)
  })

  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })
  //如果有子module,递归调用installModule,重复以上操作
  module.forEachChild((child, key) => {
    //这里的key值就是module的key
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

/**
 * make localized dispatch, commit, getters and state
 * if there is no namespace, just use root ones
 */
function makeLocalContext (store, namespace, path) {
  const noNamespace = namespace === ''
  const local = {
    dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        // if (__DEV__ && !store._actions[type]) {
        //   console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
        //   return
        // }
      }

      return store.dispatch(type, payload)
    },

    commit: noNamespace ? store.commit : (_type, _payload, _options) => {
      const args = unifyObjectStyle(_type, _payload, _options)
      const { payload, options } = args
      let { type } = args

      if (!options || !options.root) {
        type = namespace + type
        // if (__DEV__ && !store._mutations[type]) {
        //   console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
        //   return
        // }
      }
      store.commit(type, payload, options)
    }
  }

  // getters and state object must be gotten lazily
  // because they will be changed by vm update
  Object.defineProperties(local, {
    getters: {
      get: noNamespace
        ? () => store.getters
        : () => makeLocalGetters(store, namespace)
    },
    state: {
      get: () => getNestedState(store.state, path)
    }
  })

  return local
}

function makeLocalGetters (store, namespace) {
  if (!store._makeLocalGettersCache[namespace]) {
    const gettersProxy = {}
    const splitPos = namespace.length
    Object.keys(store.getters).forEach(type => {
      // skip if the target getter is not match this namespace
      if (type.slice(0, splitPos) !== namespace) return

      // extract local getter type
      const localType = type.slice(splitPos)

      // Add a port to the getters proxy.
      // Define as getter property because
      // we do not want to evaluate the getters in this time.
      Object.defineProperty(gettersProxy, localType, {
        get: () => store.getters[type],
        enumerable: true
      })
    })
    store._makeLocalGettersCache[namespace] = gettersProxy
  }

  return store._makeLocalGettersCache[namespace]
}
/**
 * 添加Mutation函数
*/
function registerMutation (store, type, handler, local) {
  //函数首先通过 type 拿到对应的 mutation 对象数组， 其实就是定义的this._mutations
  const entry = store._mutations[type] || (store._mutations[type] = [])//这里没有重名就给添加一个对象数组
  //添加方法到this._mutations
  entry.push(function wrappedMutationHandler (payload) {
    //更改this指向,传入local下的state,payload用户传入的参数
    handler.call(store, local.state, payload)
  })
}

function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

function registerGetter (store, type, rawGetter, local) {
  if (store._wrappedGetters[type]) {
    // if (__DEV__) {
    //   console.error(`[vuex] duplicate getter key: ${type}`)
    // }
    return
  }
  store._wrappedGetters[type] = function wrappedGetter (store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}

function enableStrictMode (store) {
  store._vm.$watch(function () { return this._data.$$state }, () => {
    // if (__DEV__) {
    //   assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
    // }
  }, { deep: true, sync: true })
}

function getNestedState (state, path) {
  return path.reduce((state, key) => state[key], state)
}

function unifyObjectStyle (type, payload, options) {
  if (isObject(type) && type.type) {
    options = payload
    payload = type
    type = type.type
  }

  // if (__DEV__) {
  //   assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`)
  // }

  return { type, payload, options }
}

export function install (_Vue) {
  //这里就是use传入vue的引用,这里是还没有实例化
  if (Vue && _Vue === Vue) {
  //   if (__DEV__) {
  //     console.error(
  //       '[vuex] already installed. Vue.use(Vuex) should be called only once.'
  //     )
  //   }
    return
  }
  //保存到全局,保证唯一性
  Vue = _Vue;
  //
  applyMixin(Vue)
}
